/*
*	Projeto de CG - DEFINIR TEMA
*	Created by Julio Cezar Novais Raffaine
*
*
*/

#include "GameProject.h"

namespace gameCG {
	//global salva vidas
	int oldtime = 0;
	//Pontuacao
	int pontos = 0;

	//Game Paused
	bool game_paused = true;
	//Game Ended
	bool game_ended = false;

	//Global que verifica se deve ou não fazer o flush do arquivo de Log.
	bool g_bLF = false;
	static int spin = 0;

	//Conjunto de variaveis para obter o frame rate, e apresenta-lo na tela	
	int* font = (int*)GLUT_BITMAP_HELVETICA_18;	//Tipo de Fonte
	int bitmapHeight = 18;						//Tamanho da fonte
	int frame,time,timebase = 0;				//Variaveis para calcular a frame rate
	char sFrame[30];							//String que conterá a frame rate				

	//Atribui NULL a variável que armazenará a instância do jogo
	GameProject* GameProject::Instance = NULL;

	//---------------------------------------------------------------------------------
	GameProject* GameProject::getInstance(int argc, char** argv, bool bLog) {
		if(Instance == NULL) {
			Instance = new GameProject(argc,argv,bLog);
		}
		return Instance;
	}

	GameProject::GameProject(int argc, char** argv, bool bLog) {

		srand(10);
		
		glutInit(&argc,argv);
		
		glutInitDisplayMode(GLUT_DEPTH | GLUT_RGBA | GLUT_DOUBLE);

		/*TODO Colocar uma caixa perguntando ao usuario se quer entrar no modo
		* 	fullscreen ou não. (Podendo até colocar as opçoes de resolução)
		*/
		windowed = true;
		if(!windowed) {
			glutGameModeString("1280x800:32");
			if(glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)) {
				glutEnterGameMode();
			}
		}
		else {
			glutInitWindowPosition(50,50);
			glutInitWindowSize(800,600);
			glutCreateWindow("Game Project - Em busca da idéia perdida!!");
		}

		//Registra as funções de callback no glut
		glutDisplayFunc(GameProject::render);
		glutReshapeFunc(GameProject::reshape);
		glutKeyboardFunc(GameProject::keyboard);
		glutSpecialFunc(GameProject::speckeys);
		glutMouseFunc(GameProject::mouse);
		glutTimerFunc(frame_time, GameProject::timer, frame_time);

		//Open the log file if needed
		if(bLog)
		{
			m_Log = fopen("GAMELOG_GameProject.txt", "w");
			if(!m_Log)
				exit(0);
		}

		initGL();

		//GameStuff
		Cena = new GameScene();

		player = new Carinha();
		SceneNode* temp1 = new SceneNode(player);		
		int id = Cena->getRoot()->addChild(temp1);

		food = new Comida();
		temp1 = new SceneNode(food);
		Cena->getRoot()->addChild(temp1);
		Log("id eh %d",id);
		Log("A bolinha tem x = %f e z=%f",food->getX(), food->getZ());
		
		m_lightFlags = 7;
	}
	
	void GameProject::Release() {
		Log("Liberando os recursos utilizados e finalizando o jogo.");

		delete Cena;

		if(m_Log)
			fclose(m_Log);
		exit(0);
	}

	void GameProject::start() {
		Log("Iniciando o laço principal do jogo.");
		glutMainLoop();
	}

	void GameProject::render() {
		char pontuacao[50];
	
		glClearColor(0.8f,0.8f,0.8f,0.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		if(game_ended) {
			sprintf(pontuacao,"JOGO ENCERRADO, SUA PONTUACAO FOI DE %d!!",pontos);
			Instance->setOrthographicView();
			glPushMatrix();
			glPushAttrib(GL_COLOR);
				glLoadIdentity();
				glColor3f(0.0f,0.0f,0.0f);
				Instance->renderBitmapString(Instance->m_width/2 -160 ,Instance->m_height/2 - 10,(void *)font,pontuacao);
				Instance->renderBitmapString(Instance->m_width/2 -120 ,Instance->m_height/2 + 10,(void *)font,"PRESSIONE F2 PARA RECOMECAR!");
			glPopAttrib();
			glPopMatrix();
			Instance->resetPerspectiveView();
			glutSwapBuffers();
			return;
 		}

		//Training the use of lights
/*
		GLfloat positionL0[4] = {0.0,0.0,2.0,0.0};

		glPushMatrix();
			glRotatef((GLfloat)spin,0.0f,1.0f,0.0f);
			glLightfv(GL_LIGHT0, GL_POSITION, positionL0);
			glTranslatef(0.0f,0.0f,2.0f);
			glDisable(GL_LIGHTING);
				glColor3f(0.0f,0.0f,1.0f);
				glutSolidCube(0.5f);
			glEnable(GL_LIGHTING);
		glPopMatrix();
*/
		Instance->Cena->showScene();
		
		//Calcula o frame rate
		frame++;
		time = glutGet(GLUT_ELAPSED_TIME);
		if (time - timebase > 1000) {
			sprintf(sFrame,"FPS:%4.2f",(float)frame*1000.0/(time-timebase));
			timebase = time;		
			frame = 0;
		}

		//Coleta informacoes sobre a pontuacao
		sprintf(pontuacao,"%d",pontos);

		//Imprime algumas informações na tela
		Instance->setOrthographicView();
		glPushMatrix();
		glPushAttrib(GL_COLOR);
			glLoadIdentity();
			glColor3f(0.0f,0.0f,0.0f);
			if(game_paused) Instance->renderBitmapString(250,70,(void *)font,"JOGO PAUSADO. PRESSIONE F1!");
			Instance->renderBitmapString(30,20,(void *)font,"Joguinho da minhoca?!?!?!?");
			Instance->renderBitmapString(30,45,(void *)font,sFrame);
			Instance->renderBitmapString(30,70,(void *)font,"Esc - Quit");
			Instance->renderBitmapString(Instance->m_width - 120,20,(void *)font,"Pontuacao:");
			Instance->renderBitmapString(Instance->m_width - 40,45,(void *)font,pontuacao);
		glPopAttrib();
		glPopMatrix();
		Instance->resetPerspectiveView();

		glutSwapBuffers();
	}
	
	void GameProject::reshape(GLsizei w, GLsizei h) {
		Instance->m_width = (int) w;
		Instance->m_height = (int) h;

		if(h == 0) {
			h = 1;	//Apenas para evitar divisões por zero
		}
		
		Instance->aRatio = (GLfloat)w/(GLfloat)h;
		
		//Especifica o Viewport atual, este é uma transformação afim das 
		//coordenadas do dispositivo para as coordenadas da janela
		glViewport(0,0,w,h);

		//Atualiza a matriz de projeção atual
		Instance->setViewParameters();

		glutPostRedisplay();
	}

	void GameProject::keyboard(unsigned char key, int x, int y) {
		switch(key) {
			case 27:
				Instance->Release();
				break;
			case 'd' : Instance->ObserverRot.ry += 3;
					   if (Instance->ObserverRot.ry > 0)
					   		Instance->ObserverRot.ry -= 360;
			           break;
			case 'a' : Instance->ObserverRot.ry -= 3;
					   if (Instance->ObserverRot.ry < -360)
					   		Instance->ObserverRot.ry += 360;
			           break;
			case 's' : Instance->ObserverRot.rx += 3;
					   if (Instance->ObserverRot.rx > -10)
					   		Instance->ObserverRot.rx = -10;
			           break;
			case 'w' : Instance->ObserverRot.rx -= 3;
					   if (Instance->ObserverRot.rx < -90)
					   		Instance->ObserverRot.rx = -90;
			           break;
			case 'q' : Instance->ObserverPos.z -= 0.1;
					   if (Instance->ObserverPos.z < 0.0)
					   		Instance->ObserverPos.z = 0.0;
					   break;
			case 'e':  Instance->ObserverPos.z += 0.1;
					   if (Instance->ObserverPos.z > 20.0)
					   		Instance->ObserverPos.z = 20.0;
					   break;
			case 'l':  if(Instance->m_lightFlags & 1) {
					   		glDisable(GL_LIGHT0);
							Instance->m_lightFlags &= 6;
							//Instance->Log("It enters in the if clause ...");
					   }else {
							glEnable(GL_LIGHT0);
							Instance->m_lightFlags |= 1;
					   }
					   break;
			case 'k':  if(Instance->m_lightFlags & 2) {
					   		glDisable(GL_LIGHT1);
							Instance->m_lightFlags &= 5;
							//Instance->Log("It enters in the if clause ...");
					   }else {
							glEnable(GL_LIGHT1);
							Instance->m_lightFlags |= 2;
					   }
					   break;
			case 'p':  if(Instance->m_lightFlags & 4) {
					   		glDisable(GL_LIGHT2);
							Instance->m_lightFlags &= 3;
							//Instance->Log("It enters in the if clause ...");
					   }else {
							glEnable(GL_LIGHT2);
							Instance->m_lightFlags |= 4;
					   }
					   break;
		}
		Instance->setViewParameters();		
	}

	void GameProject::speckeys(int key, int x, int y) {
		switch(key) {
			case GLUT_KEY_UP:		if(!game_paused)
										Instance->player->setDirection(3);
					  				break;
			case GLUT_KEY_DOWN:		if(!game_paused)
										Instance->player->setDirection(2);
					  				break;
			case GLUT_KEY_LEFT:		if(!game_paused)
										Instance->player->setDirection(1);
					  				break;
			case GLUT_KEY_RIGHT:	if(!game_paused)
										Instance->player->setDirection(0);
					  				break;
			case GLUT_KEY_F1:		game_paused = (game_paused)?false:true;
									break;
			case GLUT_KEY_F2:		game_ended = (game_ended)?false:true;
									//Posicao da camera
									Instance->ObserverPos.x = 0.0f;
									Instance->ObserverPos.y = 0.0f;
									Instance->ObserverPos.z = 15.0f;
							
									//Posicao angular da camera
									Instance->ObserverRot.rx = -30.0f;
									Instance->ObserverRot.ry = 0.0f;

									Instance->player->reset();
									Instance->food->consume();
									Instance->food->resetSpeed();

									game_paused = true;
									break;
		}		
		Instance->setViewParameters();	
	}

	void GameProject::mouse(int button, int state, int x, int y) {
		switch(button) {
			case GLUT_LEFT_BUTTON:
				if(state == GLUT_DOWN) {
					spin = (spin + 30) % 360;
				}
				break;
			default:
				break;
		}
	}

	void GameProject::timer(int v) {
		int newtime = glutGet(GLUT_ELAPSED_TIME);
		int delta = newtime - oldtime;
		oldtime = newtime;

		if(((Instance->player->getX() <= Instance->food->getX()+0.4f) && (Instance->player->getX() >= Instance->food->getX()-0.4f) ) && ((Instance->player->getZ() <= Instance->food->getZ()+0.4f) && (Instance->player->getZ() >= Instance->food->getZ()-0.4f) ) ) {
			pontos++;

			if(pontos%2 == 0) {
				Instance->player->increaseSpeed();
				Instance->food->increaseSpeed();
				Instance->RotacaoMaluca();
			}
			Instance->food->consume();
		}

		if((fabs(Instance->player->getX()) > 4.5) || (fabs(Instance->player->getZ()) > 4.5)) {
			//TODO Colisao encontrada. Finalizar o jogo de forma adequada.
			game_ended = true;
			Instance->player->reset();
		}
		//Atualiza a cena
		if(!game_paused) {
			Instance->Cena->updateScene(delta);
		}
		//Envia um pedido para renderizar a cena
		glutPostRedisplay();
		glutTimerFunc(frame_time, GameProject::timer, frame_time);
	}

	void GameProject::RotacaoMaluca() {

		int signalX = (rand()%2)?1:-1;
		int signalY = (rand()%2)?1:-1;

		int quantityX = (signalX)*(1+rand()%10);
		int quantityY = (signalY)*(1+rand()%10);
	
		Instance->ObserverRot.ry += quantityY;
		if (Instance->ObserverRot.ry > 0)
			Instance->ObserverRot.ry -= 360;
		else if(Instance->ObserverRot.ry < -360)
			Instance->ObserverRot.ry += 360;

		Instance->ObserverRot.rx += quantityX;
		if (Instance->ObserverRot.rx > -10)
			Instance->ObserverRot.rx = -10;
		else if (Instance->ObserverRot.rx < -90)
			Instance->ObserverRot.rx = -90;
	}

	void GameProject::setViewParameters() {
				
		//Define a matriz de projeção atual
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		//Especifica uma matriz de projeção perspectiva com os seguintes
		//parametros (angulo, aspecto, Zminimo e Zmaximo)
		gluPerspective(viewAngle, aRatio, 0.1, 500.0);

		//Após definida a matriz de projeção, posicione a camera		
		setObserver();
		setLights();	//Ativar as luzes aqui significa colocar ela fixa no cenário, i.e. independente da camera.
	}
	
	void GameProject::setObserver() {
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		
		//setLights();	//Ativar as luzes aqui significa fazê-la seguir os movimentos da camera.
		//Parametros da Luz 2
		GLfloat diffuseL2[4] = {0.0f,0.0f,1.0f,1.0f};
		GLfloat specularL2[4] = {0.0f,0.0f,1.0f,1.0f};
		GLfloat positionL2[4] = {0.0f,2.0f,0.0f,1.0f};

		glLightfv(GL_LIGHT2, GL_DIFFUSE, diffuseL2);
		glLightfv(GL_LIGHT2, GL_SPECULAR, specularL2);
		glLightfv(GL_LIGHT2, GL_POSITION, positionL2);

		glTranslatef(-ObserverPos.x,-ObserverPos.y,-ObserverPos.z);
		glRotatef(-ObserverRot.rx, 1, 0, 0);
		glRotatef(-ObserverRot.ry, 0, 1, 0);
	}

	void GameProject::setLights() {
		//Parametros da Luz 0
		//GLfloat ambientL0[4] = {0.5,0.5,0.5,1.0};
		GLfloat diffuseL0[4] = {0.0f,1.0f,0.0f,1.0f};
		GLfloat specularL0[4] = {0.0f,1.0f,0.0f,1.0f};
		GLfloat positionL0[4] = {0.0f,0.3f,0.3f,0.0f};
		
		//glLightfv(GL_LIGHT0, GL_AMBIENT, ambientL0);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseL0);
		glLightfv(GL_LIGHT0, GL_SPECULAR, specularL0);
		glLightfv(GL_LIGHT0, GL_POSITION, positionL0);

		//Parametros da Luz 1
		GLfloat diffuseL1[4] = {1.0f,0.0f,0.0f,1.0f};
		GLfloat specularL1[4] = {1.0f,0.0f,0.0f,1.0f};
		GLfloat positionL1[4] = {0.0f,4.0f,0.0f,1.0f};

		glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuseL1);
		glLightfv(GL_LIGHT1, GL_SPECULAR, specularL1);
		glLightfv(GL_LIGHT1, GL_POSITION, positionL1);

		//Ativa o uso da luz ambiente global, independente de qualquer fonte de luz
		//glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientL0);
	}

	void GameProject::initGL() {
		//Define a cor de fundo como preto
		glClearColor(0.0f,0.0f,0.0f,0.0f);

		//Habilita o uso de texturas 2D, as texturas já devem estar carregadas
		//LoadTextures();
		//glEnable(GL_TEXTURE_2D);

		//Habilita o uso de iluminação
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0); 	//Habilita a Luz 0
		glEnable(GL_LIGHT1); 	//Habilita a Luz 1
		glEnable(GL_LIGHT2);	//Habilita a Luz 2

		//Habilita o uso do Depth Buffer
		glClearDepth(1.0f);
		glDepthFunc(GL_LESS);
		glEnable(GL_DEPTH_TEST);

		//Habilita Smooth Color Shading
		glShadeModel(GL_SMOOTH);

		//Melhora a qualidade dos calculos de perspectiva
		glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

		//Angulo de Visão da matriz perspectiva
		viewAngle = 45.0f;

		//Posicao da camera
		ObserverPos.x = 0.0f;
		ObserverPos.y = 0.0f;
		ObserverPos.z = 15.0f;

		//Posicao angular da camera
		ObserverRot.rx = -30.0f;
		ObserverRot.ry = 0.0f;

		//Ajusta a matriz de projecao e a posicao da camera
		setViewParameters();		
	}

	void GameProject::setOrthographicView() {

		glDisable(GL_LIGHTING);
		// Seleciona a matriz de projeção
		glMatrixMode(GL_PROJECTION);
		// Salva a matriz anterior, para não perder as configurações
		glPushMatrix();
		// carrega a identidade
		glLoadIdentity();
		// Seleciona uma matriz de projeção ortográfica
		gluOrtho2D(0, m_width, 0, m_height);
		// inverte o eixo y, pra baixo é positivo
		glScalef(1, -1, 1);
		// Move a origem do canto inferior esquerdo para o superior esquerdo
		glTranslatef(0, -m_height, 0);
		//Carrega a matriz de transformações
		glMatrixMode(GL_MODELVIEW);
		
	}
	
	void GameProject::resetPerspectiveView() {
		// Seleciona a matriz de projeção
		glMatrixMode(GL_PROJECTION);
		// Restaura as configurações anteriores
		glPopMatrix();
		glEnable(GL_LIGHTING);
		// Carrega a matriz de transformações
		glMatrixMode(GL_MODELVIEW);
	}

	void GameProject::renderBitmapString(float x, float y, void *font,char *string) {
		char *c;
		// Seleciona a posição onde desenharemos os caracteres
		glRasterPos2f(x, y);
		// Percorra todos os caracteres da string, imprimindo-os
		for (c=string; *c != '\0'; c++) {
			glutBitmapCharacter(font, *c);
		}
	}

	void GameProject::Log(char *chString, ...) {
		char ch[256];
		//char *pArgs;
		va_list ap;
		
		//pArgs = (char*) &chString + sizeof(chString);
		va_start(ap,chString);
		vsprintf(ch, chString, ap);
		va_end(ap);
		fprintf(m_Log,"[GameProject]: ");
		fprintf(m_Log, ch);
		fprintf(m_Log,"\n");
		
		if(g_bLF)
			fflush(m_Log);	
		}
		
	}
